#include "Triangle.h"

#include <cmath>
#include <string>
#include <iostream>

namespace
  {
  bool IsInRange(double i_left, double i_right, double i_x, double i_eps)
    {
    return i_left - i_eps < i_x && i_x < i_right + i_eps;
    }
  }

Triangle::Triangle()
  {
  }

Triangle::Triangle(const Point3D& i_a,const Point3D& i_b, const Point3D& i_c) : m_plane(i_a, i_b, i_c)
  {
  m_points[0] = i_a;
  m_points[1] = i_b;
  m_points[2] = i_c;
  }


Triangle::~Triangle(void)
  {
  }

Segment Triangle::GetSide(int i_index) const
  {
  return Segment(m_points[i_index], m_points[(i_index + 1) % 3]);
  }

Point3D Triangle::GetPoint(int i_index) const
  {
  return m_points[i_index];
  }

Point3D Triangle::GetBarycentric(const Point3D& i_point, double i_eps = 1e-8) const
  {
  if (m_plane.IsOnPlane(i_point, i_eps) == false)
    {
    throw std::string("Point is not in the triangle's plane");
    }
  double a1 = m_points[1].GetX() - m_points[0].GetX(),
         b1 = m_points[2].GetX() - m_points[0].GetX(),
         c1 = i_point.GetX() - m_points[0].GetX();

  double a2 = m_points[1].GetY() - m_points[0].GetY(),
         b2 = m_points[2].GetY() - m_points[0].GetY(),
         c2 = i_point.GetY() - m_points[0].GetY();
  
  double det = a1 * b2 - a2 * b1;
  if (fabs(det) < i_eps)
    {
    throw std::string("Invalid triangle");
    }
  double det1 = c1 * b2 - c2 * b1,
         det2 = a1 * c2 - a2 * c1;
  return Point3D((det - det1 - det2) / det, det1 / det, det2 / det);
  }

bool Triangle::IsInside(const Point3D& i_point, double i_eps = 1e-8) const
  {
  if (S() < i_eps)
    {
    return GetSide(0).IsIntersect(Segment(i_point, i_point), i_eps) || GetSide(0).IsIntersect(Segment(i_point, i_point), i_eps);
    }
  return fabs(S() - Triangle(m_points[0], m_points[1], i_point).S()
                  - Triangle(m_points[0], m_points[2], i_point).S()
                  - Triangle(m_points[2], m_points[1], i_point).S()) < i_eps;
  }

Point3D Triangle::GetIntersectionPoint(const Segment& i_segment, double i_eps) const
  {
  try
    {
    Point3D point = m_plane.GetIntersectionPoint(i_segment, i_eps);
    if (IsInside(point))
      {
      return point;
      }
    }
  catch(const std::string&)
    {
    }
  throw std::string("There are no intersection with segment");
  }

Point3D Triangle::GetIntersectionPoint(const Triangle& i_other, double i_eps) const
  {
  for (int i = 0; i < 3; ++i)
    {
    for (int j = 0; j < 3; ++j)
      {
      try
        {
        Point3D point = GetSide(i).GetIntersectionPoint(i_other.GetSide(j), i_eps);
        return point;
        }
      catch(const std::string&)
        {
        }
      }
    }
  for (int i = 0; i < 3; ++i)
    {
    try
      {
      return GetIntersectionPoint(i_other.GetSide(i), i_eps);
      }
    catch(const std::string&)
      {
      }

    try
      {
      return i_other.GetIntersectionPoint(GetSide(i), i_eps);
      }
    catch(const std::string&)
      {
      }
    }
  throw std::string("There are no intersection between triangles");
  }

bool Triangle::IsIntersect(const Triangle& i_other, double i_eps) const
  {
  try
    {
    GetIntersectionPoint(i_other, i_eps);
    return true;
    }
  catch(const std::string&)
    {
    }
  return false;
  }

double Triangle::S() const
  {
  double a = m_points[0].Dist(m_points[1]), b = m_points[1].Dist(m_points[2]), c = m_points[2].Dist(m_points[0]);
  double p = (a + b + c) / 2;
  return sqrt(p * (p - a) * (p - b) * (p - c));
  }

void Triangle::SetRandom(int i_max_value = 50)
  {
  for (int i = 0; i < 3; ++i)
    {
    m_points[i].SetRandom(i_max_value);
    }
  m_plane = Plane(m_points[0], m_points[1], m_points[2]);
  }

std::ostream& operator << (std::ostream& o_stream, const Triangle& i_tr)
  {
  o_stream << "Triangle: " << i_tr.m_points[0] << ", " << i_tr.m_points[1] << ", " << i_tr.m_points[2]; 
  return o_stream;
  }